#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#include "assert.h"
#define PORT 7000
#define QUEUE 20


void Init_fd(int *fds, int size)  //初始化存放文件描述符的数组fds
{
	for(int i = 0; i < size; ++i)
	{
		fds[i] = -1;
	}
}
 
void Insert_fd(int *fds, int fd, int size)   //向fds数组中添加文件描述符
{
	for(int i = 0; i < size; ++i)
	{
		if(-1 == fds[i])
		{
			fds[i] = fd;
			break;
		}
	}
}
 
void Delete_fd(int *fds, int fd, int size)   //删除指定的文件描述符
{
	for(int i = 0; i < size; ++i)
	{
		if(fds[i] == fd)
		{
			fds[i] = -1;
			break;
		}
	}
}

 
int main()
{
    /*
    typedef struct{
        long int fds_bits[32];
    }fd_set;
    */
   //集合
    fd_set rfds;

    int ss = ::socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    //printf("%d\n",INADDR_ANY);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    int socketoptvalue = 1;
        setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (const char*)&socketoptvalue, sizeof(int));

    /**************************
    struct sockaddr {  
     sa_family_t sin_family;//地址族
　　  char sa_data[14]; //14字节，包含套接字中的目标地址和端口信息               
　　 };
    struct sockaddr {  
        sa_family_t sin_family;//地址族
        uint16_t    sin_port;   //16位端口号
        struct in_addr sin_addr;  //32位IP地址
        char          sin_zero[8];  //不使用             
　　 };

    htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

    inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值，用于sockaddr_in.sin_addr.s_addr。

    inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。
    ****************************/
    if(::bind(ss, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1)
    {
        perror("bind");
        exit(1);
    }
    /*****************************************************
     * #include <sys/socket.h>
        int listen( int sockfd, int backlog);
        sockfd：用于标识一个已捆绑未连接套接口的描述字。
        backlog：等待连接队列的最大长度。
     * ***************************************************/
    if(::listen(ss, QUEUE) == -1)
    {
        perror("listen");
        exit(1);
    }
 
    int fds[100];  //存放文件描述符
	Init_fd(fds, 100);   //初始化fds

    Insert_fd(fds, ss, 100);   //插入sockfd
	fd_set readfds;   //定义可读的fd_set结构体


    while(1){
        int maxfd = -1;
        FD_ZERO(&readfds);

        int i = 0;
        for(; i < 100; i++){
            if(fds[i] != -1){
                if(fds[i] > maxfd){
                    maxfd = fds[i];
                }
                FD_SET(fds[i], &readfds);
            }
        }

        int n = select(maxfd + 1, &readfds, NULL, NULL,NULL);
        if(n <= 0){
            if(n < 0){
                printf("select出错，客户端程序退出\n");
                break;
            }
            printf("no event\n");
            continue;
        }


        for(int i = 0; i < 100; ++i){
            if(fds[i]!=-1 && FD_ISSET(fds[i], &readfds)){
                if(fds[i] == ss){
                    struct sockaddr_in client_addr;
                    socklen_t length = sizeof(client_addr);
    /*
    SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    参数
    sockfd：套接字描述符，该套接口在listen()后监听连接。
    addr：（可选）指针，指向一缓冲区，其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
    addrlen：（可选）指针，输入参数，配合addr一起使用，指向存有addr地址长度的整型数。
    */
    ///成功返回非负描述字，出错返回-1

    /*没有用来存储accpet返回的套接字的数组，所以只能实现server和单个client双向通信*/
                    int clifd = accept(ss, (struct sockaddr *)&client_addr, &length);
                    printf("clifd connect %d\n", clifd);
                    assert(clifd != -1);
                    Insert_fd(fds,clifd, 100);
                }else{
                    char buffer[1024];   
                    memset(buffer, 0 ,sizeof(buffer));
                    int len = ::recv(fds[i], buffer, sizeof(buffer), 0);
                    if(len <= 0){
                        close(fds[i]);
                        if(len == 0)
                            printf("cli back %d\n", fds[i]);
                        else
                            printf("cli recv fail %d\n", fds[i]);
						Delete_fd(fds, fds[i], 100);  //若事件为断开连接则删除文件描述符
                        
						continue;

                    };
                    printf("recv from cli :%s", buffer);
                    int ret = send(fds[i], "ok", 2, 0);
                    if(ret < 0){
                        printf("send fail ret = %d\n",ret);
                }
                }
            }
        }
    }


    ::close(ss);
    return 0;
}